package felix.fuse.mount.service;

import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
import com.amazonaws.services.s3.model.PartETag;
import com.amazonaws.services.s3.model.UploadPartResult;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class UploadFileToRemote implements BaseService {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private final Cache<Long, FileWriteContext> fileWriteContextCache = Caffeine.newBuilder()
            .scheduler(Scheduler.systemScheduler())
            .expireAfterAccess(Duration.ofMinutes(5))
            .maximumSize(100).build();

    //5MB
    private final int _5MB = 1024 * 1024 * 5;

    public S3Service s3service;

    public void setS3Service(S3Service s3service) {
        this.s3service = s3service;
    }

    public boolean write(String bucketName,String path, Long fileId, byte[] bytes) {
        FileWriteContext fileWriteContext = fileWriteContextCache.getIfPresent(fileId);
        int size = bytes.length;
        if (fileWriteContext == null) {
            fileWriteContext = this.initUploadPart(bucketName,path, fileId);
            fileWriteContextCache.put(fileId, fileWriteContext);
        }
        fileWriteContext.append(bytes);
        //大于等于5MB，则flush
        if (fileWriteContext.getLength() >= _5MB) {
            byte[] buffer = fileWriteContext.flushBuffer();
            int partNumber = fileWriteContext.partNumber.getAndIncrement();
            UploadPartResult uploadPartResult = writeUploadPartResult(bucketName,path, partNumber, buffer, fileWriteContext.initiateMultipartUploadResult);
            fileWriteContext.partETags.add(uploadPartResult.getPartETag());
            log.debug("UploadPartToRemote : {}", buffer.length);
        }
        return true;

    }

    public void flush(String bucketName,String path, Long fileId) {
        FileWriteContext fileWriteContext = fileWriteContextCache.getIfPresent(fileId);
        if (fileWriteContext != null) {
            if (fileWriteContext.getLength() > 0) {
                log.debug("flushToRemote : {}", fileWriteContext.getLength());
                byte[] buffer = fileWriteContext.flushBuffer();
                int partNumber = fileWriteContext.partNumber.getAndIncrement();
                UploadPartResult uploadPartResult = writeUploadPartResult(bucketName,path, partNumber, buffer, fileWriteContext.initiateMultipartUploadResult);
                fileWriteContext.partETags.add(uploadPartResult.getPartETag());
            }
            this.s3service.completeMultipartUploadRequest(bucketName,path,fileWriteContext.initiateMultipartUploadResult, fileWriteContext.partETags);
            fileWriteContextCache.invalidate(fileId);
        }
    }

    private void writeAllLess5M(String bucketName,String path, Long fileId, byte[] bytes) {
        this.s3service.putObject(bucketName,path, bytes);
    }

    private FileWriteContext initUploadPart(String bucketName, String path, Long fileId) {
        InitiateMultipartUploadResult initiateMultipartUploadResult = this.s3service.initiateMultipartUploadResult(bucketName,path);
        FileWriteContext fileWriteContext = new FileWriteContext();
        fileWriteContext.initiateMultipartUploadResult = initiateMultipartUploadResult;
        fileWriteContext.partETags = new ArrayList<>();
        fileWriteContext.partNumber = new AtomicInteger(1);
        return fileWriteContext;
    }

    private UploadPartResult writeUploadPartResult(String bucketName, String path, Integer partNumber, byte[] bytes, InitiateMultipartUploadResult initResponse) {
        return s3service.uploadPart(bucketName, path, partNumber, bytes, initResponse);
    }

    @Override
    public void init() {

    }

    @Override
    public void destroy() {

    }

    public class FileWriteContext {
        public InitiateMultipartUploadResult initiateMultipartUploadResult;
        public AtomicInteger partNumber;
        public List<PartETag> partETags;
        private byte[] buffer;
        private int length = 0;

        public int getLength() {
            return length;
        }

        public byte[] flushBuffer() {
            byte[] returnBytes = new byte[length];
            System.arraycopy(buffer, 0, returnBytes, 0, length);
            length = 0;
            return returnBytes;
        }

        public void append(byte[] bytes) {
            if (buffer == null) {
                buffer = new byte[_5MB];
                length = 0;
            }
            System.arraycopy(bytes, 0, buffer, length, bytes.length);
            length += bytes.length;
        }

    }
}
